function ZIP() {
    var t = this, args = arguments, nargs = args.length,
        instOf = function(e,t){
            return e && e.hasOwnProperty && e instanceof t
        };
    
    instOf(t, ZIP) ? t.init() : t = new ZIP();
    
    if (nargs) {
        for (; i < nargs; i++) {
            //do something **********
        }
    }
    
    return t;

};

( function() {
    // construct ZIP's static funcs and prototype

    // private variables
    var $win = window,
        $doc = $win.document,
        domHead = $doc.getElementsByTagName('head')[0],
        isInstanceOf = function(inst, _Type){
            return inst && inst.hasOwnProperty && inst instanceof _Type
        };

    var ZIP_id = 'ZIP_0';  // the global ID of the ZIP function-object itself

    // our registry variables -----------------------------
    var urls = {
            // the uris for each module.  populated by loading dynamically (other than the root ZIP )
            // Structure of each entry :- modName: { uri, cached, cache, requires }
            // Once loaded the cached prop is set to true
            ZIP: { uri: 'ZIP.js', cached: true, cache: 'ZIP', requires: null },      // the root, by default this is always true
            DOM: {
                uri: 'ZIP-DOM.js',
                cached: false,
                cache: ['Cache', 'DOM'],        // essentially means the mod is stored as ZIP.Cache.DOM
                requires: ['ZIP']
            },
            AppWindow: {
                uri: 'ZIP-Widget-AppWindow.js',
                cached: false,
                cache: ['Cache', 'Widget', 'AppWindow'],    // ZIP.Cache.Widget.AppWindow
                requires: ['ZIP']
            },
            ZIPConsole: {
                uri: 'ZIP-Widget-Console.js',
                cached: false,
                cache: ['Cache', 'Widget', 'Console'],
                requires: ['ZIP', 'AppWindow']
            },
            ObjectTracker: {
                uri: 'ZIP-Widget-ObjectTracker.js',
                cached: false,
                cache: ['Cache', 'Widget', 'ObjectTracker'],
                requires: ['ZIP', 'AppWindow', 'ZIPConsole']
            },
            AJAX: {
                uri: 'ZIP-AJAX.js',
                cached: false,
                cache: ['Cache', 'AJAX'],
                requires: ['ZIP']
            }
        },
        mList = {  // modules V instances
           ZIP: [ZIP_id]     // array of inst ids for this module
           // modX: [], ...  add a line per module added
        },
        sTags = [],     // script tag IDs, created when a new upload occurs and consulted for deletion when a threshold is reached
        timers = [];	// note this is an array of references to timer objects
        
            
    // functios to manage and manipulate our registry -----------------------------------------

    function processAysnchTask(task, taskComplete, taskAbort, checkPeriod, stopPeriod) {
        // Function to setoff an asynch task.
        // task : function that defines the task to perform.. eg (add scriptTag element
        //        to the <head> which will make the browser kick off the script loading process
        // taskComplete: check function to be called periodically to check if conditions
        //               have been met and if so, to stop further checking
        //                  must return a true/false checking status
        // taskAbort: function called when the stopPeriod is reached; this is a fail safe in
        //            case the taskComplete conditions are not met and we're looping forever..
        //            ie. we kill the process dead
        //              always good practice to return true/false of abort function
        // checkPeriod: time period to use for the periodic taskComplete check
        // stopPeriod: the overall perios within which this task should complete & if not abort
        //------------------------------------
        
        // we'll wrap all our task related functions, in order to be able to defer the task start
        var taskStarted = false, checkTimer, stopTimer,
            wrapTask = ( function(t) {
                return function() {
                    taskStarted = true;
                    t();
                }
            } ) (task),
            wrapTaskComplete = ( function(doCheck) {
                return function() {
                    taskStarted && doCheck() && clearInterval(checkTimer);
                }
            } ) (taskComplete),
            wrapTaskAbort = ( function(abort) {
                return function() {
                    taskStarted && clearInterval(checkTimer) && abort();
                }
            } ) (taskAbort);
        
        
        // ensure we have default values 
        checkPeriod = checkPeriod || 100;
        stopPeriod = stopPeriod || 5000;
        
        // start the task in a few moments.. gives us time to setup the rest
        setTimeout(wrapTask, 500);

        // start the checking process
        checkTimer = setInterval(wrapTaskComplete, checkPeriod);
        
        //************ later add timer to timer list for houekeeping **************
        
        // set up the abort timer
        stopTimer  = setTimeout(wrapTaskAbort, stopPeriod);

        //************ later add timer to timer list for houekeeping **************

        
    }
    function isCached(mod) {
        // function used to check directly if the module has loaded into the cache.
        // a quick check to see if module cached already
        if ( urls[mod].cached ) return true;
        
        // we may still be loading/parsing the module and not have had a chance to set the flag
        // so check for physical evidence.. ie test for the subfolder existence in ZIP.
        var i = 0, cache = urls[mod].cache, n = cache.length,
            base = ZIP;     //  all modules are cached into the ZIP space, when loaded

        // walk ZIP's cache folder
        for (; i < n; i++) {
            if ( !(base = base[cache[i]]) ) return false
            //base = base[cache[i]];
        }
        
        // if you're here then the mod *is* cached, but the flag has not been set yet
        return urls[mod].cached = true;     // set the cached flag and return true.. not good science to
                                            // pollute a checking function.
    }
    function getURIdefinition(mod) {
        // return the uri from our defined set of urls in our registry above.
        // null if it does not exst.
        return urls[mod].uri ? urls[mod].uri : null;
    }
    function registerWithInstance(mod, inst) {
console.log('registerWithInstance: ', mod, inst.id, isRegisteredWithInstance(mod, inst));

        // register module with the instance
        //                      the use of || means that if mList[mod] exists,
        //                      then we're finished with the check;
        //                      we return mList[mod]
        //                               |
        //                      +----------------------------------------+
        //return mod && inst.id && ( mList[mod] || ((mList[mod]=mList[mod]||[]) && mList[mod].push(inst.id)) );
        return mod && inst.id && (
            isRegisteredWithInstance(mod, inst) ||
            ((mList[mod]=mList[mod]||[]) && mList[mod].push(inst.id)) );

    }
    function isRegisteredWithInstance(mod, inst) {
        // does the instance have the module attached to it
        // simple check to see if mList[mod] has the inst id listed
        return ( mList[mod] && (mList[mod].indexOf(inst.id) > -1) );
    }
    
    function sTagsAdd(domRefToScriptTag) {
        return sTags.push(domRefToScriptTag);
    }
    function addScript(uri) {
        // we create a script element with source set to the uri, then add it to the <head> of the document.
        // by adding it to the <head> element, we set the loading in motion
        if (!uri || (uri == '*internal*')) return false;
        var sRef;
        
        sRef = $doc.createElement('script');
        sRef.src = uri;
        sRef.id = system.uid('SCRIPT');

        // append the element to the <head> and keep ref to it in the sTags list
        domHead.appendChild(sRef);
        sTagsAdd(sRef);
        return sRef;
    }
    function addCSS(uri) {      // ********* placeholder .. need to setup houskeeping for CSS loadings
        if (!uri) return false;
        var sRef;
        
        sRef = $doc.createElement('link');
        sRef.rel = "stylesheet";
        sRef.type = "text/css";
        sRef.href = uri;
        sRef.id = system.uid('CSS');

        // append the element to the <head> and keep ref to it in the sTags list
        domHead.appendChild(sRef);
        return sRef;
    }
    function defineScript(mod, uri, cachePath) {
        // if scripts outside of the well defined ones (as set in urls registry) need to be loaded,
        // this method allows us to extend the urls set.
        // cachePath format: Cache.a.b.c ..
        var url;
        if (urls[mod]) return false;    // the mod exists; use replaceScript if you really want to change it
        
        url = urls[mod] = [];
        if ( uri && (url.uri = uri) && (url.cached = false) && (url.cache = cachePath.split['.']) ) {
            return true;
        }
        
        // above setup failed so cleanup and return
        delete urls[mod];
        return false;
        
    }
    function defineCSS(uri) {  //********* placeholder
    }
    function addModuleToInstance(m, inst) {
        // instantiate/attach the module for the given instance
        var i = 0, base = inst || ZIP,     // if inst is null then we're doing it for the ZIP space
            cachePath = urls[m].cache,
            n = cachePath.length,
            sub, ourModule = ZIP;
console.log('addModuleToInstance mod = '+m, inst.id);

        // locate the 'folder' in ZIP.Cache first
        for (; i < n-1; i++) {
            sub = cachePath[i];
            // we create the sub-folder structure for the instance; note that we ignore the 'Cache' sub-folder
            // hence the (i>0) check
            (i>0) && ( base[sub] = base[sub] || {} ) &&  ( base=base[sub] );
            ourModule = ourModule[sub];           // and locate where our module is in ZIP.Cache.
        }
        sub = cachePath[i];
        ourModule = ourModule[sub];
console.log('addModuleToInstance sub = '+sub, base, ourModule.toString());

        // instantiate if necessary.  return the reference to the cache area
        return ( base[sub] || (base[sub] = ourModule(inst)) );
    }

    var proto = {
        init: function () {
            var self = this;
            if (self instanceof ZIP) self.id =  ZIP.System.uid('ZIP');
            registerWithInstance('ZIP', self);
            return self;
        },
        
        upload: function(mod, callbackFunction, callingInst) {
        //**** assume loading just one module at a time for now.
        //**** this method uses the html <script> tag approach to loading.  for XHR methodology
        //**** then use the uploadXHR method.
            //
            // NOTES on requirements for each module:
            //0. Each module will be loaded into the ZIP.Cache folder initially.
            //1. Each module file will start with ZIP.namespace('Cache') - to create the Cache 'folder' in the ZIP space
            //2. Next the module definition will be in the form ZIP.Cache.<module name> = function(param) { ..code.. };
            //
            //3. Once loaded into the Cache folder, we instantiate the code into ZIP.<module name> from Cache as follows:
            //      ZIP.<module name> = ZIP.Cache.<module name>( param );
            //
            // Thereafter each instance of ZIP will get a copy of the module from the Cache folder by following step 3:
            //      <instance of ZIP>.<module name> = ZIP.Cache.<module name>( param );
            //
        
            var self = this, config, sRef,
                forInst = callingInst || this,
                callback = callbackFunction || function(){},
                isLoaded, abortLoad;

ZIP.Console.log('upload 1: loading into = ', self, forInst );

            if ( !isInstanceOf(self, ZIP) ) { 
                // we are called as ZIP.upload so we are uploading the mod from source file
                // if callingInst is set, then we were called by an instance of ZIP, otherwise
                // we were called directly as ZIP.upload(mod, callback|)
                
                // check for existence with 'self'.. in this case self = ZIP.
                if ( isRegisteredWithInstance(mod, self) ) return self;
ZIP.Console.log('upload 2: mod = ', mod, callback);


                isLoaded = ( function(m, cb) {
                    return ( function() {
                        if ( !isCached(m) ) return false;
                        
                        addModuleToInstance(m, self);       // add to self (ZIP) space first, ie. instantiate
                        registerWithInstance(m, self);
                        addModuleToInstance(m, forInst);    // now add it to the instance space, ie. instantiate
                        registerWithInstance(m, forInst);
                        cb(forInst);
                        return true;
                    } );
                } )(mod, callback);
                
                abortLoad = function() {
                    ZIP.Console.log('INFO.. ', mod, ' upload aborted');
                    return true;
                }
                processAysnchTask( ( function() { addScript(getURIdefinition(mod)) } ), isLoaded, abortLoad, 100, 5000);
            } 
            else {
                //  we were called as <instance>.upload(module, callback), check if module is already part of this instance.
                if ( !(isRegisteredWithInstance(mod, self)) ) {
console.log('upload 3 (new ZIP()): ', self.id, mod, isCached(mod));

                    // if not in cache, call ZIP.upload()
                    if ( !isCached(mod) ) {
                        ZIP.upload(mod, callback, self);
                    }
                    else {
                        addModuleToInstance(mod, self);
                        registerWithInstance(mod, self);
                        callback(self);
                    }
                }
            }

            return self;
        },
        uploadXHR: function() {
            // for later ****************
        },

        use: function(mod, appCode) {
            var t = this, reqList, nReqs, i = 0;
            
            // check if dependencies are uploaded
            reqList = urls[mod].requires.slice(0);  // copy the urls array; we dont want to mangle the original
            reqList.push(mod);
            nReqs = reqList.length;
console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>> use 1: ', mod, reqList);

            function allLoaded() {
                for (i = 0; i < nReqs; i++) {
                    if ( !isCached(reqList[i]) ) return false;
                }
                for (i = 0; i < nReqs; i++) {
                    // the ZIP.add would have cached the module.. here we attach it to the instance
                    t.upload(reqList[i], function() {} );   
                }
console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>> use 2: ',t);

                return true;
            }
            
            function goWhenAllLoaded() {
//ZIP.Console.log('----- use: checking if all loaded');

                if ( allLoaded() ) {
                    // and now for the app bit!!
                    appCode(t);
                }
                else {
                    ZIP.System.fnNextTick(goWhenAllLoaded);     // check again in the next tick
                }
            }
            goWhenAllLoaded();
        
        },

        namespace: function(space) {
            // for now we'll just deal with setting up one name space at a time.
            // the space path is spec'd in the format: s1.s2.s3.etc
            // the returned object will be <this>.path

            var t = this,  // set the base object
                pSects = [],
                nSect,
                j;

            if ( space.indexOf('.') > -1 )
            {
                // composite path
                pSects = space.split('.');  // get the individual components of the path
                nSect = pSects.length;
                j = 0;

                // create the appropriate bits of the path
                for (; j < nSect; j++) {
                    t[ pSects[j] ] = t[ pSects[j] ] || {};
                    t = t[ pSects[j] ];
                }
            
            } else {
                // simple path
                t[space] = t[space] || {};
                t = t[space];
            }
            return t;
        },

        run: function(callback) {
            var self = this;
            return self;
        }
    }  // proto
    
    
    var system = {
        // Our object factory //////////////////////////////////////////////////////
        object: function(o) {
            var F = function() {};
            F.prototype = o;
            return new F();
        },
        // unique ID generator for any arbitrary type of object/////////////////////////////
        uid: ( function() {
            var _uid = [],
                defaultType = 'ZIP',
                seedInt = 1000;     // always reserver the 1st 1000 of any type

            return function(uidType) {
                uidType = uidType || defaultType;
                _uid[uidType] = _uid[uidType] ? ++_uid[uidType] : seedInt;
                return ( [uidType, '_', _uid[uidType]].join('') );
            }
        } ) (),
        // copy functions //////////////////////////////////////////////////////
        deepCopy: function(src, dest) {
            var dst = dest || {};  // if dest is not present then create a new object
            for (var prop in src) {
                if (typeof src[prop] == "object") {	// if the prop is an object then check if
                                                    // it is an Array "[]" or an object "{}
                    dst[prop] = src[prop].constructor === Array ? [] : {};  // could have used instanceof to check for array
                    ZIP.System.deepCopy(src[prop], dst[prop]);  // recursive call to copy the prop
                }
                else {
                   dst[prop] = src[prop];
                }
            }
            return dst;
        },
        shallowCopy: function(src, dest) {
            var dst = dest || {};  // if dest is not present then create a new object
            
            for (var prop in src) {
                dst[prop] = src[prop];
            }
            return dst;
        },
        // merge 2 objects.
        // - return an object where props common to both mean that slave's version is used
        // - any props in slave, not in master are ignored
        // - if master contains props not existent in slave, then copy master's props.
        replace: function(master, slave) {
            var dst = {};
            
            // copy over master
            dst = ZIP.System.deepCopy(master);
            if (!slave) return dst;
            
            // replace from slave if prop is common to both
            for (var prop in slave) {
                if (master[prop]) {
                    // replace master's copy
                    dst[prop] = ( (typeof slave[prop] == 'object') 
                                    ? ( (typeof master[prop] == 'object') 
                                          ? ZIP.System.replace(master[prop], slave[prop])  // recursive call
                                          : ZIP.System.deepCopy(slave[prop])
                                      )
                                    : slave[prop]
                                );
                }
            }
            return dst;
        },
        // extend an object.  (aka deepCopy)
        // extend the master with the props from extension.  returns an extended master
        extend: function(master, extension) {
            ZIP.System.deepCopy(extension, master);
            return master;
        },

        getObjectStrMap: function(obj) {
            // return a stack of strings, each entry is the string rep of a property in the obj, 
            // together with type and the depth of that prop in the object
            // eg. obj = {a:someval, b:{x: {q:val, ...}} }
            // the string rep to the prop q of x of b in obj is 'b.x.q' and the depth is 2
            
            var stackCtr = 0, j = 0,
                fqnStack = [/*strRep, type, level*/];       // type ( 0 = not object, 1 = object )
            
            function getStackStringToDepth(str, d) {
                return ( (str.split('.').slice(0,d)).join('.') );
            }
            
            function recurseObject(o, d) {
                for (var i in o ) {	
                    stackCtr++;
                    
                    fqnStack[stackCtr] = ['', 1, d-1];
                    fqnStack[stackCtr][0] = getStackStringToDepth(fqnStack[stackCtr-1][0], d);
                    fqnStack[stackCtr][0] = [fqnStack[stackCtr][0],i].join('.');
                    
                    if ( typeof o[i] == 'object' ) {
                        recurseObject(o[i], d+1);
                    }
                    else {
                        fqnStack[stackCtr][1] = 0;  // not an object
                    }
                }
            };
            
            fqnStack[0] = ['Start', 1, 0];     // this is just to seed the recursion, will be spliced out
            recurseObject(obj, 1);
            
            // remove 'Start.' from each of the rep strings in the stack
            fqnStack.splice(0,1);           // splice out the 1st element, which was a seed for the recursion
            for (; j < fqnStack.length; j++) {
                fqnStack[j][0] = fqnStack[j][0].replace('Start.', '');
            }
            return fqnStack.sort(); // return a sorted (by alphabet) list
        },
        // get the key, val pair from an object given the string rep'ng the onject /////////////////////////
        getKeyVal: function(kStr, obj) {
            var j = 0,
                _name = null,
                _value = obj || null,
                _type = null,
                _keys = kStr?kStr.split('.') : {};

            if (!_value ) return null;

            for (; j < _keys.length; j++) {
                _name = _keys[j];
                _type = typeof _value[_name];
                if ( _type == "undefined") {
                    _value = 'does not exist';
                    break;
                }
                _value = _value[_name];
             }
             return {
                key: _name,
                value: _value,
                type: _type
             }
        },

        //  place holder ***************************************************************
        mapObjectToDOM: function(obj, objStack, objDOM) {
            var idx, val = '';
            for ( var i = 0; i < objStack.length; i++) {
                if ( objStack[i][1] == 0 ) {
                    idx = objStack[i][0].split('.');
                    val = obj;
                    for ( var j = 0; j < idx.length; j++) {
                        if (typeof val[idx[j]] == "undefined") {
                            val = 'does not exist';
                            break;
                        }
                        val = val[idx[j]];
                    }
                    console.log(objStack[i][0], ' = ', val);
                    
                }
            }
        },

        // a sophisticated version of the 'typeof' construct /////////////////////////////////
        type: (function(global) {
            var cache = {};
            return function(obj) {
                var key;
                return obj === null ? 'null' // null
                    : obj === global ? 'WINDOW' // window in browser or global in nodejs
                    : (key = typeof obj) !== 'object' ? key // basic: string, boolean, number, undefined, function
                    : obj.nodeType ? 'DOMobject' // DOM element
                    : cache[key = ({}).toString.call(obj)] // cached. date, regexp, error, object, array, math
                    || (cache[key] = key.slice(8, -1).toLowerCase()); // get XXXX from [object XXXX], and cache it
            };
        }) (this),
        
        // binding an object to a method
        bindTo: function(obj, f) {
            // are we going to bind any params to f also??
            var args = ZIP.System.toArray(arguments);
            args.splice(0,2);  // remove obj and f from the arg vector

            return function() { 
                f.apply(obj, args);
            }
        },

        // method to q up methods whilst an async process is in progress
        //
        queue: function() {
            var _q = [],
            _response = null,
            _self = this,
            _flushed = false;

            return{
                add: function(fn) {
                    // only q it up if the q is still alive
                    if (!_flushed) {
                        _q.push(fn);
                    }
                    else {
                        return fn(_response);
                    }
                    return this;
                },

                flush: function(r) {
                    if (_flushed) return this;  // means the q has been unblocked and flushed already
                    _flushed = true;
                    _response = r;
                    
                    // pop the functions in FIFO order and execute.
                    while(_q[0]) {
                        _q.shift()(r);
                    }
                    return this;
                },
                
                // cosmetic method for debugging purps only
                showQ: function() { ZIP.Console.log('Showing Q: ', _q); return this; }
            }
        },

        // sometimes we just want a way of delaying or waiting in order to test timing related functionality
        // this method is recommended only for testing purposes and in no way should it be used for production
        // quality/release code... note that the browser just hangs whilst the delay method is in execution.
        wait: function(ms){
            ms += new Date().getTime();
            while (new Date() < ms){}
        },
        setStopWatch: function() {
            var baseTime = (new Date()).getTime();
            return function() { return ((new Date()).getTime() - baseTime); }
        },
        // queue up the process
        fnNextTick: function(f) {
            setTimeout(f, 1);
        },
        // helper method to convert to an array.  used mainly to convert an arguments vector into an array
        toArray: function(args) {
            return Array.prototype.slice.call(args);
        }
    };  // system
    
    ZIP.id = ZIP_id;
    ZIP.Cache = {};     // all modules are cached here.
    ZIP.Console = console;
    
    ZIP.prototype = proto;      // for use during new ZIP(..) calls.
    
    for (var f in proto) {
        proto.hasOwnProperty(f) && (ZIP[f] = proto[f]);
    }

    ZIP.namespace('System');
    for (f in system) {
        system.hasOwnProperty(f) && (ZIP.System[f] = system[f]);
    }
//////////////////////////////////////////////////////////////////////////////////////////////////////
//Add a custom module to the system
//
    ZIP.add = function(mod, customModuleCode, version, config) {
        // first ensure that all required modules as per what's stated in the config object,
        // are loaded.  config object: config = { requires :[m1, m2,...]
        //
console.log('ZIP.add 1', mod, config);

        var i = 0, reqList = [],
            nReqs, t = this, ns;

        config && config.requires && (reqList = config.requires.slice(0));
        
        function _addToUrls( mod, config ) {
            urls[mod] = ZIP.System.deepCopy(config);
        }

        // add the module to ZIP
        if (mod) {
            ZIP.Cache[mod] = customModuleCode;
            reqList.unshift('ZIP'); // always 'ZIP' !
            _addToUrls( mod, {uri: '*internal*', cached: true, cache: ['Cache', mod], requires: reqList} );
        }
        
        // now kick off the uploads for the 'requires'
        nReqs = reqList.length
        for (; i < nReqs; i++) {
console.log('ZIP.add loading 2', reqList[i]);
            t.upload(reqList[i], function() {} );
        }
    }
//////////////////////////////////////////////////////////////////////////////////////////////////////

    ZIP.Debug = {
        getURLSregistry: function(mod) {
            var arr = [];
            
            ( mod && urls[mod] && (arr[mod]=urls[mod]) ) || (arr['urls'] = urls);
            return arr;
        },
        getCachefolder: function() { return ZIP.Cache },
        getmList: function(mod) {
            var arr = [];
            
            ( mod && mList[mod] && (arr[mod]=mList[mod]) ) || (arr['mList'] = mList);
            return arr;
        },
        getsTags: function() { return sTags },
        getTimers: function() { return timers },
        getObject: function(subObj) {
            var arr = [];
            
            ( subObj && ZIP[subObj] && (arr[subObj]=ZIP[subObj]) ) || (arr['ZIP'] = ZIP);
            return arr;
        }
    };

} )();





